﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
using XNAGameEngine.Logs;

namespace XNAGameEngine.Audio
{
    public sealed class ManagerAudio2D : DrawableGameComponent, IManagerAudio2D
    {
        #region local fields

        private bool _isLooping;

        private int currentIndex;

        string _defaultDirectory;
        List<SoundEffectInstance> _soundEffects = new List<SoundEffectInstance>();
        List<SoundEffectInstance> _doneSFX = new List<SoundEffectInstance>();

        private DataStructures.Deque<string> _songQueue = new DataStructures.Deque<string>();

        #endregion

        #region constructors

        public ManagerAudio2D(Game game)
            : this(game, "")
        { }

        public ManagerAudio2D(Game game, string defaultDirectory)
            : base(game)
        {
            game.Services.AddService(typeof(IManagerAudio2D), this);
            currentIndex = 0;
            _defaultDirectory = defaultDirectory;
            _isLooping = false;
            MediaPlayer.ActiveSongChanged += new EventHandler(MediaPlayer_ActiveSongChanged);
            MediaPlayer.MediaStateChanged += new EventHandler(MediaPlayer_MediaStateChanged);
            ManagerLogs.Write("ManagerAudio: Created");
        }

        #endregion

        #region local methods

        /// <summary>
        /// This method has been registered with a listener; whenever the active song
        /// is changed the listener will call this method.
        /// </summary>
        /// <param name="sender">The listener that has been registered with this method will be the sender</param>
        /// <param name="e">This is the event argument data that is passed in</param>
        void MediaPlayer_ActiveSongChanged(object sender, EventArgs e)
        {
            if (_songQueue.Count > 0)
            {
                Console.WriteLine(_songQueue._First + " " + currentIndex);
            }
        }

        /// <summary>
        /// This method is registered with the MediaStateChanged event listener.  Whenever the media
        /// changes and it changes to being stopped; the next song in the song queue will be played.
        /// </summary>
        /// <param name="sender">The listener that has been registered with this method will be the sender</param>
        /// <param name="e">This is the event argument data that is passed in</param>
        void MediaPlayer_MediaStateChanged(object sender, EventArgs e)
        {

            if (MediaPlayer.State == MediaState.Stopped && _songQueue.Count > 0)
            {
                if (currentIndex == _songQueue.Count)
                {
                    if (_isLooping)
                        NextSong();
                }
                else
                {
                    NextSong();
                }
            }
        }

        #endregion

        #region IManagerAudio2D Members

        /// <summary>
        /// Get or set Song volume
        /// </summary>
        public float _SongVolume
        {
            get { return MediaPlayer.Volume; }
            set { if (value >= 0.0f && value <= 1.0f) MediaPlayer.Volume = value; }
        }

        /// <summary>
        /// Get or set whether or not the song queue is looping
        /// </summary>
        public bool _IsLooping
        {
            get { return _isLooping; }
            set { _isLooping = value; }
        }

        /// <summary>
        /// This method will start the song queue if there are no songs currently playing
        /// </summary>
        public void StartSong()
        {
            if (_songQueue.Count > 0 && MediaPlayer.State.Equals(MediaState.Stopped))
            {
                currentIndex = 1;
                MediaPlayer.Play(GameEngine.GameContent.Load<Song>(_defaultDirectory + _songQueue._First));
            }
        }

        /// <summary>
        /// This method adds the specified song to the play queue
        /// </summary>
        /// <param name="songName">The song to be added to the que</param>
        public void AddSongToQueue(string songName)
        {
            if (_songQueue.Contains(songName))
            {
                _songQueue.AddLast(songName);
            }
            else
            {
                _songQueue.AddLast(songName);
                Game.Content.Load<Song>(_defaultDirectory + _songQueue._Last);

            }

        }

        /// <summary>
        /// This method will remove a song from the play queue.
        /// </summary>
        /// <param name="songName">The song to be removed</param>
        public bool RemoveSongFromQueue(string songName)
        {
            if (_songQueue.Contains(songName))
            {
                _songQueue.Remove(songName);
                return true;
            }
            return false;
        }

        /// <summary>
        /// This method will play the next song in the play queue
        /// </summary>
        public void NextSong()
        {
            if (currentIndex == _songQueue.Count)
            {
                currentIndex = 0;
            }
            if ((_songQueue.Count > 0) && currentIndex != _songQueue.Count)
            {
                _songQueue.AddLast(_songQueue._First);
                _songQueue.RemoveFirst();
                MediaPlayer.Play(Game.Content.Load<Song>(_defaultDirectory + _songQueue._First));
                currentIndex++;
            }
        }

        /// <summary>
        /// This method will play the previous song in the play queue
        /// </summary>
        public void PreviousSong()
        {
            if (currentIndex < 2)
            {
                currentIndex = _songQueue.Count + 1;
            }
            if (_songQueue.Count > 0 && currentIndex > 0)
            {
                _songQueue.AddFirst(_songQueue._Last);
                _songQueue.RemoveLast();
                MediaPlayer.Play(Game.Content.Load<Song>(_defaultDirectory + _songQueue._First));
                currentIndex--;
            }
        }

        /// <summary>
        /// This method will clear the queue so that it can be repopulated with new content
        /// </summary>
        public void ClearQueue()
        {
            MediaPlayer.Stop();
            _songQueue.Clear();
            
        }

        /// <summary>
        /// Play a song
        /// </summary>
        /// <param name="songName">the name of the song</param>
        /// <param name="looping">whether it loops or not</param>
        public void PlaySong(string songName, bool looping)
        {
            _isLooping = looping;
            if (Game.Content.Load<Song>(_defaultDirectory + songName).Name != _defaultDirectory + _songQueue._First)
            {
                MediaPlayer.Play(Game.Content.Load<Song>(_defaultDirectory + songName));
                _songQueue.AddFirst(songName);
            }
        }

        /// <summary>
        /// Play a song (no repeat)
        /// </summary>
        /// <param name="songName">the name of the song</param>
        public void PlaySong(string songName)
        { PlaySong(songName, false); }

        /// <summary>
        /// Stops the song (no resume available)
        /// </summary>
        public void StopSong()
        { 
            MediaPlayer.Stop();
        }

        /// <summary>
        /// Pauses the song
        /// </summary>
        public void PauseSong()
        { MediaPlayer.Pause(); }

        /// <summary>
        /// Resumes the paused song
        /// </summary>
        public void ResumeSong()
        { MediaPlayer.Resume(); }

        /// <summary>
        /// Plays a sound.  
        /// Note insure that the soundfile you are playing has the correct properties.
        ///      i.e.  Content importer: mp3 audio
        ///            Content processor: sound effect
        /// </summary>
        /// <param name="soundName">name of the sound</param>
        public void PlaySound(string soundName)
        { PlaySound(soundName, 1.0f, 0.0f, 0.0f, false); }

        /// <summary>
        /// Plays a sound
        /// Note insure that the soundfile you are playing has the correct properties.
        ///      i.e.  Content importer: mp3 audio
        ///            Content processor: sound effect
        /// </summary>
        /// <param name="soundName">name of the sound</param>
        /// <param name="volume">volume (0.0f to 1.0f)</param>
        public void PlaySound(string soundName, float volume)
        { PlaySound(soundName, volume, 0.0f, 0.0f, false); }

        /// <summary>
        /// Plays a sound
        /// Note insure that the soundfile you are playing has the correct properties.
        ///      i.e.  Content importer: mp3 audio
        ///            Content processor: sound effect
        /// </summary>
        /// <param name="soundName">name of the sound</param>
        /// <param name="volume">volume (0.0f [mute] to 1.0f [maximum])</param>
        /// <param name="pitch">pitch (-1.0f [-1 octave] to 1.0f [+1 octave])</param>
        /// <param name="pan">pan (-1.0f [left] to 1.0f [right]. 0.0f is center</param>
        /// <param name="loop">whether the sound loops</param>
        /// <returns name="temp">returns the created sound effect so that the user can stop it at their leasure, if loop = true</returns>
        public SoundEffectInstance PlaySound(string soundName, float volume, float pitch, float pan, bool loop)
        {
            SoundEffectInstance temp = Game.Content.Load<SoundEffect>(_defaultDirectory + soundName).CreateInstance();
            temp.IsLooped = loop;
            temp.Pan = pan;
            temp.Pitch = pitch;
            temp.Play();
            _soundEffects.Add(temp);

            return (loop) ? temp : null;
        }

        /// <summary>
        /// This function handles stopping looped sound effects only.
        /// There is a check to insure that the sound effect is in the sound effect list before
        /// it adds it to the done list.
        /// </summary>
        /// <param name="soundToStop">a sound effect that was returned from the PlaySound function</param>
        public void StopSoundEffect(SoundEffectInstance soundToStop)
        {
            if (_soundEffects.Contains(soundToStop))
            {
                _doneSFX.Add(soundToStop);
                soundToStop.Stop(true);
            }
        }

        /// <summary>
        /// Restarts a song from the beginning
        /// </summary>
        /// <param name="looping">whether the song loops</param>
        public void RestartSong(bool looping)
        {
            if (_songQueue._First != null)
            {
                _isLooping = looping;
                MediaPlayer.Play(Game.Content.Load<Song>(_defaultDirectory + _songQueue._First));
            }
        }

        /// <summary>
        /// Restarts a song from the beginning (looping is unchanged from last play)
        /// </summary>
        public void RestartSong()
        { RestartSong(_isLooping); }

        /// <summary>
        /// Stops song and all sounds
        /// </summary>
        public void DestroySounds()
        {
            foreach (SoundEffectInstance sfx in _soundEffects)
            {
                _doneSFX.Add(sfx);
            }

            StopSong();
        }

        /// <summary>
        /// cleans up sound effects that are done playing
        /// </summary>
        /// <param name="gameTime">game time</param>
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            foreach (SoundEffectInstance sfx in _doneSFX)
            {
                sfx.Dispose();
                _soundEffects.Remove(sfx);
            }

            _doneSFX.Clear();


            foreach (SoundEffectInstance sfx in _soundEffects)
            {
                if (sfx.State == SoundState.Stopped)
                {
                    _doneSFX.Add(sfx);
                }
            }
        }
        #endregion
    }
}



